﻿using System;
using System.Collections.Generic;
using Sharp3DMath;
using Sharp3DMath.Core;
using Sharp3DMath.Geometry2D;
using Sharp3DMath.Strokes;
using System.Text;
using System.ComponentModel;
using System.Runtime.InteropServices;


namespace Sharp3DMath.Strokes
{
    public enum StrokeDataType
    {
        Position,
        PositionTime
    }
    public enum StrokeType
    {
        Line,
        Circle,
        Unknow
    }
    public class Stroke
    {
        [DllImport("kernel32.dll")]
        private static extern bool QueryPerformanceCounter(ref long x);
        [DllImport("kernel32.dll")]
        private static extern bool QueryPerformanceFrequency(ref long x);

        private StrokeDataType dataType;
        private StrokeType strokeType;

        static readonly int Max_Count = 100;
        #region Private Fields
        private List<Vector2F> points; //记录本次笔画的所有点

        private List<double> pointsDirection ;//记录点的逻辑方向

        private List<long> pointsTime;//记录所有点的时间

        private List<double> pointsSpeed ;//记录所有点的速度

        private List<double> pointsCurvature ;//记录所有点的曲率

        private List<Vector2F> vertexs ; //记录探测顶点
        #endregion

        #region Public Properties
        
        public List<double> Directions
        {
            get { return this.pointsDirection; }
        }

        public List<double> Speeds
        {
            get
            {
                if (dataType == StrokeDataType.Position)
                {
                    return null;
                }
                return this.pointsSpeed;
            }
        }
        public List<double> Curvatures
        {
            get { return this.pointsCurvature; }
        }

        public List<Vector2F> Points
        {
            get { return points; }
            set { points = value; }
        }

        public bool Empty
        {
            get 
            {
                return points.Count == 0;
            }
        }
        #endregion


        private long frequency;

        public long Frequency
        {
            set { this.frequency = value; }
        }

        private double strokeLength = -1;
        public double StrokeLength
        {
            get 
            {
                if (strokeLength<=0)
                {
                    strokeLength = points.Count > 0 ? GetStrokeLength() : 0;
                }
                return strokeLength;
            }
            set { strokeLength = value; }
        }

        #region Public Constructor
        public Stroke()
        {
           points = new List<Vector2F>(Max_Count); 

           pointsDirection = new List<double>(Max_Count);

           pointsTime = new List<long>(Max_Count);

           pointsSpeed = new List<double>(Max_Count);

           pointsCurvature = new List<double>(Max_Count);

           vertexs = new List<Vector2F>(Max_Count / 10);

           if (QueryPerformanceFrequency(ref frequency) == false)
           {
               // Frequency not supported
               throw new Win32Exception();
           }
           dataType = StrokeDataType.PositionTime;
           strokeType = StrokeType.Unknow;
        }
        public Stroke(List<Vector2F> data)
            :this(data,StrokeDataType.Position)
        {
        }
        public Stroke(Vector2F[] data)
            :this(data,StrokeDataType.Position)
        {
        }
        public Stroke(List<Vector2F> data, StrokeDataType DataType)
        {
            pointsTime = new List<long>() ;

            LoadPointsFrom(data);
            pointsDirection = new List<double>(points.Count);
            pointsSpeed = new List<double>(points.Count);
            pointsCurvature = new List<double>(points.Count);
            vertexs = new List<Vector2F>(points.Count / 10);

            if (QueryPerformanceFrequency(ref frequency) == false)
            {
                // Frequency not supported
                throw new Win32Exception();
            }
            dataType = DataType;
            strokeType = StrokeType.Unknow;
        }
        public Stroke(Vector2F[] data, StrokeDataType DataType)
        {
            pointsTime = new List<long>();

            LoadPointsFrom(data);
            pointsDirection = new List<double>(points.Count);
            pointsSpeed = new List<double>(points.Count);
            pointsCurvature = new List<double>(points.Count);
            vertexs = new List<Vector2F>(points.Count / 10);

            if (QueryPerformanceFrequency(ref frequency) == false)
            {
                // Frequency not supported
                throw new Win32Exception();
            }

            dataType = DataType;
            strokeType = StrokeType.Unknow;
        }

        public Stroke(Stroke strCopy)
        {
 
        }

        #endregion
        
        /// <summary>
        /// add the point ,and tick the time;
        /// </summary>
        /// <param name="pt"></param>
        public void AddActivePoint(Vector2F pt)
        {
            AddPoint(pt);
            long t = 0;
            QueryPerformanceCounter(ref t);
            pointsTime.Add(t);
        }

        public void AddPoint(Vector2F pt)
        {
            points.Add(pt);
        }

        #region Public Initialize Function
        public void LoadPointsFrom(string path)
        {
            this.points = FileDataTools.ReadDataToList(path);
            if (points.Count < 7)
            {   //throw excpeion
                points = null;
            }
        }
        public void LoadPointsFrom(Vector2F[] data)
        {
            if (data.Length < 7)
            {//throw excpeion
                points = new List<Vector2F>(Max_Count);
            }
            this.points = new List<Vector2F>(data);
        }
        public void LoadPointsFrom(List<Vector2F> data)
        {
            if (data.Count < 7)
            {//throw excpeion
                points = new List<Vector2F>(Max_Count);
            }
            this.points = new List<Vector2F>(data);
        }
        #endregion

        #region Private Fucntion
        private void ComputeStrokeSpeed()
        {
            if (points.Count < 7)
            {// should never happen
                return;
            }

            double tm1, tm2, dt, ds;
            int dcount = points.Count;
            for (int i = 0; i < dcount - 1; i++)
            {
                tm2 = pointsTime[i + 1];
                tm1 = pointsTime[i];
                dt = (tm2 - tm1) / frequency;
                ds = DistanceMethods.Distance(points[i], points[i + 1]);
                pointsSpeed.Add(ds / dt);
            }
            pointsSpeed.Add(pointsSpeed[dcount - 2]);
            MathEx.UnNormalizingArray(pointsSpeed);
        }
        private void ComputeStrokeDirection()
        {
            if (points.Count < 7)
            {
                return;
            }
            else
            {

                Vector2F[] pt = new Vector2F[7];
                pointsDirection.Add(0);
                pointsDirection.Add(0);
                pointsDirection.Add(0);
                for (int i = 3; i <= points.Count - 4; i++)
                {
                    double k = 0.0, b = 0.0;
                    int indx = 0;
                    for (int j = i - 3; j <= i + 3; j++)
                    {
                        pt[indx++] = points[j];
                        System.Diagnostics.Debug.WriteLine(points[j].ToString());
                    }

                    MathEx.FitToLine(pt, ref k, ref b);
                    System.Diagnostics.Debug.WriteLine(k);

                    pointsDirection.Add(MathEx.Atan(k));
                }

                pointsDirection[0] = pointsDirection[3];

                pointsDirection[1] = pointsDirection[3];

                pointsDirection[2] = pointsDirection[3];


                pointsDirection.Add(pointsDirection[points.Count - 4]);
                pointsDirection.Add(pointsDirection[points.Count - 4]);
                pointsDirection.Add(pointsDirection[points.Count - 4]);


            }


        }
        private void ComputeStrokeCurvature()
        {
            if (points.Count < 7)
            {
                return;
            }
            #region
            StrokeAnalyzer.ComputeStrokeCurvature(this);
            #endregion
        }
        private double GetStrokeLength()
        {
            if (points.Count == 0)
            {
                return 0;
            }
            return StrokeAnalyzer.GetStrokeLength(points);
        }
        #endregion

        public void Clear()
        {
            this.points.Clear();
            this.pointsCurvature.Clear();
            this.pointsDirection.Clear();
            this.pointsTime.Clear();
            this.pointsSpeed.Clear();

            if (QueryPerformanceFrequency(ref frequency) == false)
            {
                // Frequency not supported
                throw new Win32Exception();
            }
            strokeLength = -1;
           
        }
        
        public Vector2F[] GetStrokePoints(int sIndex, int length)
        {
            if (sIndex < 0 || length < 0 || (sIndex + length) > (this.points.Count))
            {//
                return null;
            }
            Vector2F[] points = new Vector2F[length];
            
            for (int i = 0; i < length; i++)
            {
                points[i] = new Vector2F(this.points[sIndex + i]);
            }
            return points;
        }

        #region Compute Data
        public void Do()
        {
            switch (dataType)
            {
                case StrokeDataType.Position:
                    ComputeStrokeDirection();
                    ComputeStrokeCurvature();
                    strokeLength = GetStrokeLength();
                    break;
                case StrokeDataType.PositionTime:
                    ComputeStrokeSpeed();

                    ComputeStrokeDirection();
                    ComputeStrokeCurvature();
                    strokeLength = GetStrokeLength();
                    break;
                default:
                    break;
            }
            
        }
        #endregion

        public static Stroke Zero
        {
            get
            {
                return new Stroke();
            }
        }
    }
}
